#include "backdrop_scroll.h"
#include "backdrop_layer.h"

#include "graphics/graphics_util.h"

#include "util/timer.h"		    // Needs to come in early
#include "util/mmgr.h"		    // Needs to come in early
#include "util/common_macros.h"		// macros
#include "util/log.h"		    // logging

using namespace backdrop;

BackdropScroll::BackdropScroll(camera::FbCamera* pCam) : Backdrop(pCam),
	m_Dest(0.0f, 0.0f),
	m_ScrollSpeed(1.0f, 1.0f),
	m_Size(1.0f, 1.0f),
	m_IsScrolling(false)
{
}

BackdropScroll::~BackdropScroll()
{
	Clear();
}

Decal* BackdropScroll::GetDecalSprite(const math::Vec2& Pos)
{
	int i;
	Decal* pDecal = NULL;

	for (i = (int)(m_Foreground.size() - 1); i >= 0; i--)
	{
		if (pDecal = m_Foreground[i]->GetDecalSprite(Pos))
			return pDecal;
	}

	for (i = (int)(m_Background.size() - 1); i >= 0; i--)
	{
		if (pDecal = m_Background[i]->GetDecalSprite(Pos))
			return pDecal;
	}

	return Backdrop::GetDecalSprite(Pos - GetOffset());
}

Decal* BackdropScroll::GetDecalSprite(const math::Vec2& Pos, int Layer, bool Background)
{
	if (Background)
		return m_Background[Layer]->GetDecalSprite(Pos);
	else
		return m_Foreground[Layer]->GetDecalSprite(Pos);
}

bool BackdropScroll::DeleteDecalSprite(Decal* pDecal)
{
	unsigned int i;

	for (i = 0; i < m_Foreground.size(); i++)
	{
		if (m_Foreground[i]->DropDecalSprite(pDecal))
			return true;
	}

	for (i = 0; i < m_Background.size(); i++)
	{
		if (m_Background[i]->DropDecalSprite(pDecal))
			return true;
	}

	return Backdrop::DeleteDecalSprite(pDecal);
}

void BackdropScroll::ResetDecalPos()
{
	unsigned int i;

	for (i = 0; i < m_Background.size(); i++)
		m_Background[i]->ResetDecals();

	for (i = 0; i < m_Foreground.size(); i++)
		m_Foreground[i]->ResetDecals();

	Backdrop::ResetDecalPos();
}

void BackdropScroll::PreRender()
{
	bool bEnableSnap = GraphicsUtil::SnapToPixel;
	GraphicsUtil::SnapToPixel = !m_IsScrolling;

	for (unsigned int i = 0; i < m_Background.size(); i++)
		m_Background[i]->Render();

	GraphicsUtil::SnapToPixel = bEnableSnap;

	Backdrop::PreRender();
}

void BackdropScroll::PostRender()
{
	bool bEnableSnap = GraphicsUtil::SnapToPixel;
	GraphicsUtil::SnapToPixel = !m_IsScrolling;

	for (unsigned int i = 0; i < m_Foreground.size(); i++)
		m_Foreground[i]->Render();

	GraphicsUtil::SnapToPixel = bEnableSnap;

	Backdrop::PostRender();
}

void BackdropScroll::Tick(float dt)
{
	Backdrop::Tick(dt);

	unsigned int i;

	for (i = 0; i < m_Background.size(); i++)
		m_Background[i]->Tick(dt);

	for (i = 0; i < m_Foreground.size(); i++)
		m_Foreground[i]->Tick(dt);

	if (m_IsScrolling)
	{
		math::Vec2 Offset;
		float newdist;

		math::Vec2 dir = m_Dest - m_Offset;
		float dist = dir.Magnitude();

		dir.Normalize();
		dir = dir * (m_ScrollSpeed.Magnitude() * dt);
		Offset = m_Offset + dir;

		// check if reached destination
		dir = m_Dest - Offset;
		newdist = dir.Magnitude();

		if ((newdist < 0.000500f) || (newdist > dist)) {
			Offset = m_Dest;
			m_IsScrolling = false;
		}

		Offset = Offset - m_Offset;
		Scroll(Offset);
	}
}

void BackdropScroll::Clear()
{
	Backdrop::Clear();

	m_Offset = math::Vec2(0.0f, 0.0f);
	m_Dest = math::Vec2(0.0f, 0.0f);
	m_IsScrolling = false;
}

void BackdropScroll::ClearLevel()
{
	unsigned int i;

	for (i = 0; i < m_Background.size(); i++)
		UTIL_SAFE_DELETE(m_Background[i]);

	for (i = 0; i < m_Foreground.size(); i++)
		UTIL_SAFE_DELETE(m_Foreground[i]);

	m_Background.clear();
	m_Foreground.clear();

	Backdrop::ClearLevel();
}

void BackdropScroll::SaveSpecial(script::WriteLua& Output)
{
	unsigned int i;

	Output.StartTable("Scene");
	Output.WritePair("Width", m_Size.x);
	Output.WritePair("Height", m_Size.y);
	Output.CloseTable(false);

	if (m_Background.size() + m_Foreground.size())
	{
		std::vector<BackdropLayer*>* pList[2] = { &m_Background, &m_Foreground };
		Output.StartTable("Layers");

		for (int j = 0; j < 2; j++)
		{
			for (i = 0; i < (unsigned int)pList[j]->size(); i++)
			{
				if ((*pList[j])[i]->IsEmpty()) continue;
				Output.StartTable();

				Output.WritePair("Image", (*pList[j])[i]->GetName());
				Output.WritePair("Pos", (*pList[j])[i]->GetPos());
				Output.WritePair("Size", (*pList[j])[i]->GetSize());
				Output.WritePair("Parallax", (*pList[j])[i]->GetParallax());
				Output.WritePair("Foreground", j);
				Output.WritePair("UVScroll", (int)((*pList[j])[i]->IsUVScroll() ? 1 : 0));

				(*pList[j])[i]->SaveDecals(Output);

				Output.CloseTable();
			}
		}

		Output.CloseTable(false);
	}
}

void BackdropScroll::LoadSpecial(LuaPlus::LuaState* pScript)
{
	LuaPlus::LuaObject Scene = script::ScriptHelper::GetObj(*pScript, "Scene");
	if (Scene.IsTable())
	{
		float Width = script::ScriptHelper::GetTableFloat(Scene, "Width", 1.0f);
		float Height = script::ScriptHelper::GetTableFloat(Scene, "Height", 1.0f);
		
		SetSize(math::Vec2(Width, Height));
	}

	LuaPlus::LuaObject Layers = script::ScriptHelper::GetObj(*pScript, "Layers");
	if (Layers.IsTable())
	{
		int Count = Layers.GetTableCount();
		for (int i = 0; i < Count; i++)
		{
			LuaPlus::LuaObject LayerData = Layers.GetByIndex(i+1);
			std::string Image = script::ScriptHelper::GetTableString(LayerData, "Image", "");
			math::Vec2 Pos = script::ScriptHelper::GetTableVec2(LayerData, "Pos");
			math::Vec2 Size = script::ScriptHelper::GetTableVec2(LayerData, "Size", math::Vec2(1.0f, 1.0f));
			float Parallax = script::ScriptHelper::GetTableFloat(LayerData, "Parallax", 1.0f);
			bool Foreground = script::ScriptHelper::GetTableInt(LayerData, "Foreground", 0) ? true : false;
			bool UVScroll = script::ScriptHelper::GetTableInt(LayerData, "UVScroll", 0) ? true : false;

			if (BackdropLayer* pLayer = AddLayer(-1, Parallax, !Foreground, Image, Pos, Size, UVScroll)) {
				LuaPlus::LuaObject DecalData = LayerData.GetByName("Decals");
				if (DecalData.IsTable())
					pLayer->LoadDecals(DecalData);
			}
		}
	}
}

BackdropLayer* BackdropScroll::AddLayer(int LayerID, float Parallax, bool Background, const std::string& Name,
										const math::Vec2& Pos, const math::Vec2& Size, bool UVScroll)
{
	if (Background)
		return AddLayer(&m_Background, Name, LayerID, Parallax, Pos, Size, UVScroll);
	else
		return AddLayer(&m_Foreground, Name, LayerID, Parallax, Pos, Size, UVScroll);
}

BackdropLayer* BackdropScroll::AddLayer(std::vector<BackdropLayer*>* pList, const std::string& Image, int LayerID,
										float ParallaxSpeed, const math::Vec2& Pos, const math::Vec2& Size, bool UVScroll)
{
	BackdropLayer* pLayer = NULL;
	int idx = LayerID;

	if (LayerID >= (int)pList->size())
		LayerID = -1;

	pLayer = new BackdropLayer(this);

	if (!pLayer->Create(Image, UVScroll, ParallaxSpeed)) {
		UTIL_SAFE_DELETE(pLayer);
		return NULL;
	}

	if (UVScroll)
	{
		pLayer->Setup(Pos, Size);
		pLayer->Scroll(math::Vec2(), false);
	}
	else
	{
		math::Vec2 realSize;
		pLayer->GetRealSize(realSize);
		pLayer->Setup(Pos, realSize);
	}

	if (idx == -1)
		pList->push_back(pLayer);
	else
		pList->insert(pList->begin() + idx, pLayer);

	return pLayer;
}

unsigned int BackdropScroll::GetLayerC(bool Background)
{
	if (Background)
		return (unsigned int)m_Background.size();
	else
		return (unsigned int)m_Foreground.size();
}

BackdropLayer* BackdropScroll::GetLayer(unsigned int idx, bool Background)
{
	if (Background)
		return m_Background[idx];
	else
		return m_Foreground[idx];
}

void BackdropScroll::InsertLayer(BackdropLayer* pLayer, int idx, bool Background)
{
	if (Background)
	{
		if ((idx >= 0) && (idx < (int)m_Background.size()))
			m_Background.insert(m_Background.begin() + idx, pLayer);
		else
			m_Background.push_back(pLayer);
	}
	else
	{
		if ((idx >= 0) && (idx < (int)m_Foreground.size()))
			m_Foreground.insert(m_Foreground.begin() + idx, pLayer);
		else
			m_Foreground.push_back(pLayer);
	}
}

void BackdropScroll::DeleteLayer(BackdropLayer* pLayer, bool bDelete)
{
	unsigned int i;

	for (i = 0; i < m_Background.size(); i++)
	{
		if (pLayer == m_Background[i]) {
			m_Background.erase(m_Background.begin() + i);
			if (bDelete)
				UTIL_SAFE_DELETE(pLayer);
			return;
		}
	}

	for (i = 0; i < m_Foreground.size(); i++)
	{
		if (pLayer == m_Foreground[i]) {
			m_Foreground.erase(m_Foreground.begin() + i);
			if (bDelete)
				UTIL_SAFE_DELETE(pLayer);
			return;
		}
	}
}

void BackdropScroll::SetLayerEnabled(unsigned int idx, bool bEnable, bool Background)
{
	if (BackdropLayer* pLayer = GetLayer(idx, Background))
		pLayer->SetIsEnabled(bEnable);
}

void BackdropScroll::Scroll(const math::Vec2& pos, const bool bRelative, const bool bDelay)
{
	unsigned int i;
	math::Vec2 Dest;
	math::Vec2 Offset = pos;

	if (LockHorizontalScroll()) Offset.x = bRelative ? 0.0f : m_Offset.x;
	if (LockVerticalScroll()) Offset.y = bRelative ? 0.0f : m_Offset.y;

	if (bRelative)
		Dest = m_Offset + Offset;
	else
		Dest = Offset;

	// bounds checking
	if (ClampToLeft())
	{
		if (Dest.x < 0.0f) { Offset.x -= Dest.x; Dest.x = 0.0f; }
		else if ((Dest.x + 1.0f) > m_Size.x) { Offset.x -= (Dest.x + 1.0f - m_Size.x); Dest.x = m_Size.x - 1.0f; }
	}
	else
	{
		if (Dest.x > 0.0f) { Offset.x -= Dest.x; Dest.x = 0.0f; }
		else if (Dest.x < (1.0f - m_Size.x)) { Dest.x += (1.0f - Dest.x - m_Size.x); Dest.x = 1.0f - m_Size.x; }
	}

	if (ClampToTop())
	{
		if (Dest.y < 0.0f) { Offset.y -= Dest.y; Dest.y = 0.0f; }
		else if ((Dest.y + 1.0f) > m_Size.y) { Offset.y -= (Dest.y + 1.0f - m_Size.y); Dest.y = m_Size.y - 1.0f; }
	}
	else
	{
		if (Dest.y > 0.0f) { Offset.y -= Dest.y; Dest.y = 0.0f; }
		else if (Dest.y < (1.0f - m_Size.y)) { Offset.y += (1.0f - Dest.y - m_Size.y); Dest.y = 1.0f - m_Size.y; }
	}

	if (bDelay) {
		m_IsScrolling = true;
		m_Dest = Dest;
	} else {
		m_Offset = Dest;
		for (i = 0; i < m_Background.size(); i++) m_Background[i]->Scroll(Offset, bRelative);
		for (i = 0; i < m_Foreground.size(); i++) m_Foreground[i]->Scroll(Offset, bRelative);
	}
}

void BackdropScroll::SetSize(const math::Vec2& size)
{
	math::Vec2 offset = m_Offset;
	bool UpdateOffset = false;

	m_Size = size;

	if (ClampToLeft()) {
		if ((offset.x + 1.0f) > m_Size.x) { offset.x = m_Size.x - 1.0f; UpdateOffset = true; }
	} else {
		if ((offset.x - 1.0f) < -m_Size.x) { offset.x = 1.0f - m_Size.x; UpdateOffset = true; }
	}

	if (ClampToTop()) {
		if ((offset.y + 1.0f) > m_Size.y) { offset.y = m_Size.y - 1.0f; UpdateOffset = true; }
	} else {
		if ((offset.y - 1.0f) < -m_Size.y) { offset.y = 1.0f - m_Size.y; UpdateOffset = true; }
	}

	if (UpdateOffset) Scroll(offset - m_Offset);
}
